package com.gitee.wsl.func.filter.bloomfilter

import com.gitee.wsl.ext.base.SIZE
import com.gitee.wsl.func.filter.bloomfilter.api.BitMapExtractor
import com.gitee.wsl.func.filter.bloomfilter.api.BloomFilter
import com.gitee.wsl.func.filter.bloomfilter.api.Hasher
import com.gitee.wsl.func.filter.bloomfilter.api.IndexExtractor
import com.gitee.wsl.func.filter.bloomfilter.bean.BitMaps
import com.gitee.wsl.func.filter.bloomfilter.bean.Shape
import kotlin.IllegalArgumentException
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertNotSame
import kotlin.test.assertTrue


/**
 * Tests standard methods in the [BloomFilter] interface.
 */
abstract class AbstractBloomFilterTest<T : BloomFilter<T>> {
    /**
     * Test fixture class returns the value as the only value.
     */
    class BadHasher(value: Int) : Hasher {
        var extractor: IndexExtractor = IndexExtractor.fromIndexArray(value)

        public override fun indices(shape: Shape): IndexExtractor {
            return extractor
        }
    }

    private fun assertFailedIndexExtractorConstructor(shape: Shape, values: IntArray) {
        val indices = IndexExtractor.fromIndexArray(*values)
        assertFailsWith<IllegalArgumentException>{ createFilter(shape, indices) }
    }

    private fun assertIndexExtractorMerge(shape: Shape, values: IntArray, expected: IntArray) {
        val indices= IndexExtractor.fromIndexArray(*values)
        val filter = createFilter(shape, indices)
        val lst = ArrayList<Int>()
        filter.processIndices({ x ->
            lst.add(x)
            true
        })
        assertEquals(expected.size, lst.size)
        for (value in expected) {
            assertTrue(lst.contains(value), "Missing " + value)
        }
    }

    /**
     * Creates an empty version of the BloomFilter implementation we are testing.
     *
     * @param shape the shape of the filter.
     * @return a BloomFilter implementation.
     */
    protected abstract fun createEmptyFilter(shape: Shape): T

    /**
     * Creates the BloomFilter implementation we are testing.
     *
     * @param shape the shape of the filter.
     * @param extractor A BitMap extractor to build the filter with.
     * @return a BloomFilter implementation.
     */
    protected fun createFilter(shape: Shape, extractor: BitMapExtractor): T {
        val bf = createEmptyFilter(shape)
        bf.merge(extractor)
        return bf
    }

    /**
     * Creates the BloomFilter implementation we are testing.
     *
     * @param shape the shape of the filter.
     * @param hasher the hasher to use to create the filter.
     * @return a BloomFilter implementation.
     */
    protected fun createFilter(shape: Shape, hasher: Hasher): T {
        val bf = createEmptyFilter(shape)
        bf.merge(hasher)
        return bf
    }

    /**
     * Creates the BloomFilter implementation we are testing.
     *
     * @param shape the shape of the filter.
     * @param extractor An Index extractor to build the filter with.
     * @return a BloomFilter implementation.
     */
    protected fun createFilter(shape: Shape, extractor: IndexExtractor): T {
        val bf = createEmptyFilter(shape)
        bf.merge(extractor)
        return bf
    }

    protected val testShape: Shape
        /**
         * The shape of the Bloom filters for testing.
         *
         *  * Hash functions (k) = 17
         *  * Number of bits (m) = 72
         *
         * @return the testing shape.
         */
        get() = Shape.fromKM(17, 72)

    /**
     * Tests that asBitMapArray works correctly.
     */
    @Test
    fun testAsBitMapArray() {
        // test when multiple long values are returned.

        val hasher = IncrementingHasher(63, 1)
        val bf = createFilter(Shape.fromKM(2, 72), hasher)
        val lb: LongArray = bf.asBitMapArray()
        assertEquals(2, lb.size)
        assertEquals(0x800000000000000, lb[0])
        assertEquals(0x1, lb[1])
    }

    @Test
    fun testBitMapExtractorSize() {
        val idx = IntArray(1)
        createFilter(this.testShape, TestingHashers.FROM1).processBitMaps({ i ->
            idx[0]++
            true
        })
        assertEquals(BitMaps.numberOfBitMaps(this.testShape), idx[0])

        idx[0] = 0
        createEmptyFilter(this.testShape).processBitMaps({ i ->
            idx[0]++
            true
        })
        assertEquals(BitMaps.numberOfBitMaps(this.testShape), idx[0])
    }

    @Test
    open fun testCardinalityAndIsEmpty() {
        testCardinalityAndIsEmpty(createEmptyFilter(this.testShape))
    }

    /**
     * Tests cardinality and isEmpty. Bloom filter must be able to accept multiple
     * IndexExtractor merges until all the bits are populated.
     *
     * @param bf The Bloom filter to test.
     */
    protected fun testCardinalityAndIsEmpty(bf: BloomFilter<*>) {
        assertTrue(bf.isEmpty())
        assertEquals(0, bf.cardinality())
        for (i in 0..<this.testShape.numberOfBits) {
            bf.merge(IndexExtractor.fromIndexArray(i))
            assertFalse(bf.isEmpty(), "Wrong value at $i")
            assertEquals(i + 1, bf.cardinality(), "Wrong value at $i")
        }

        // check operations in reverse order
        bf.clear()
        assertEquals(0, bf.cardinality())
        assertTrue(bf.isEmpty())
        for (i in 0..<this.testShape.numberOfBits) {
            bf.merge(IndexExtractor.fromIndexArray(i))
            assertEquals(i + 1, bf.cardinality(), "Wrong value at " + i)
            assertFalse(bf.isEmpty(), "Wrong value at " + i)
        }
    }

    @Test
    fun testClear() {
        val bf1 = createFilter(this.testShape, TestingHashers.FROM1)
        assertNotEquals(0, bf1.cardinality())
        bf1.clear()
        assertEquals(0, bf1.cardinality())
    }

    @Test
    fun testContains() {
        var bf1 = createFilter(this.testShape, TestingHashers.FROM1)
        val bf2 = TestingHashers.populateFromHashersFrom1AndFrom11(createEmptyFilter(this.testShape))

        assertTrue(bf1.contains(bf1), "BF1 Should contain itself")
        assertTrue(bf2.contains(bf2), "BF2 Should contain itself")
        assertFalse(bf1.contains(bf2), "BF1 should not contain BF2")
        assertTrue(bf2.contains(bf1), "BF2 should contain BF1")

        assertTrue(bf2.contains(IncrementingHasher(1, 1)), "BF2 Should contain this hasher")
        assertFalse(bf2.contains(IncrementingHasher(1, 3)), "BF2 Should not contain this hasher")

        var indexExtractor = IncrementingHasher(1, 1).indices(this.testShape)
        assertTrue(bf2.contains(indexExtractor), "BF2 Should contain this hasher")
        indexExtractor = IncrementingHasher(1, 3).indices(this.testShape)
        assertFalse(bf2.contains(indexExtractor), "BF2 Should not contain this hasher")

        var bitMapExtractor = BitMapExtractor.fromIndexExtractor(
            IncrementingHasher(1, 1).indices(this.testShape),
            this.testShape.numberOfBits
        )
        assertTrue(bf2.contains(bitMapExtractor), "BF2 Should contain this hasher")
        bitMapExtractor = BitMapExtractor.fromIndexExtractor(
            IncrementingHasher(1, 3).indices(this.testShape),
            this.testShape.numberOfBits
        )
        assertFalse(bf2.contains(bitMapExtractor), "BF2 Should not contain this hasher")

        // Test different lengths
        bf1 = createFilter(this.testShape, TestingHashers.FROM1)
        val bf3 = createFilter(
            Shape.fromKM(this.testShape.numberOfHashFunctions, Long.SIZE - 1),
            TestingHashers.FROM1
        )
        assertTrue(bf1.contains(bf3))
        assertTrue(bf3.contains(bf1))

        val bf4 = TestingHashers.populateRange(
            createEmptyFilter(Shape.fromKM(this.testShape.numberOfHashFunctions, Long.SIZE - 1)), 1,
            11 + this.testShape.numberOfHashFunctions
        )

        assertFalse(bf1.contains(bf4))
        assertTrue(bf4.contains(bf1))
    }

    @Test
    fun testCopy() {
        testCopy(true)
    }

    protected fun testCopy(assertClass: Boolean) {
        val bf1= createFilter(this.testShape, TestingHashers.FROM1)
        assertNotEquals(0, bf1.cardinality())
        val copy= bf1.copy()
        assertNotSame(bf1, copy)
        assertContentEquals(bf1.asBitMapArray(), copy.asBitMapArray())
        assertContentEquals(bf1.asIndexArray(), copy.asIndexArray())
        assertEquals(bf1.cardinality(), copy.cardinality())
        assertEquals(bf1.characteristics(), copy.characteristics())
        assertEquals(bf1.estimateN(), copy.estimateN())
//        if (assertClass) {
//            assertEquals(bf1.getClass(), copy.getClass())
//        }
        assertEquals(bf1.shape, copy.shape)
        assertEquals(bf1.isEmpty(), copy.isEmpty())
        assertEquals(bf1.isFull(), copy.isFull())
    }

    @Test
    fun testEmptyAfterMergeWithNothing() {
        // test the case where is empty after merge
        // in this case the internal cardinality == -1
        val bf = createEmptyFilter(this.testShape)
        bf.merge(IndexExtractor.fromIndexArray())
        assertTrue(bf.isEmpty())
    }

    /**
     * Tests that the estimated intersection calculations are correct.
     */
    @Test
    fun testEstimateIntersection() {
        val bf = createFilter(this.testShape, TestingHashers.FROM1)
        val bf2= TestingHashers.populateFromHashersFrom1AndFrom11(createEmptyFilter(this.testShape))

        val bf3 = TestingHashers.populateEntireFilter(createEmptyFilter(this.testShape))

        assertEquals(1, bf.estimateIntersection(bf2))
        assertEquals(1, bf2.estimateIntersection(bf))
        assertEquals(1, bf.estimateIntersection(bf3))
        assertEquals(1, bf2.estimateIntersection(bf))
        assertEquals(2, bf3.estimateIntersection(bf2))

        val bf4 = createEmptyFilter(this.testShape)

        assertEquals(0, bf.estimateIntersection(bf4))
        assertEquals(0, bf4.estimateIntersection(bf))

        val midPoint: Int = this.testShape.numberOfBits / 2
        val bf5= TestingHashers.populateRange(createEmptyFilter(this.testShape), 0, midPoint)
        val bf6 = TestingHashers.populateRange(
            createEmptyFilter(this.testShape),
            midPoint + 1,
            this.testShape.numberOfBits - 1
        )
        assertFailsWith<IllegalArgumentException>{ bf5.estimateIntersection(bf6) }

        // infinite with infinite
        assertEquals(Int.Companion.MAX_VALUE, bf3.estimateIntersection(bf3))
    }

    /**
     * Tests that the size estimate is correctly calculated.
     */
    @Test
    fun testEstimateN() {
        // build a filter
        var filter1 = createFilter(this.testShape, TestingHashers.FROM1)
        assertEquals(1, filter1.estimateN())

        // the data provided above do not generate an estimate that is equivalent to the
        // actual.
        filter1.merge(IncrementingHasher(4, 1))
        assertEquals(1, filter1.estimateN())

        filter1.merge(IncrementingHasher(17, 1))

        assertEquals(3, filter1.estimateN())

        filter1 = TestingHashers.populateEntireFilter(createEmptyFilter(this.testShape))
        assertEquals(Int.Companion.MAX_VALUE, filter1.estimateN())
    }

    /**
     * Tests that the estimated union calculations are correct.
     */
    @Test
    fun testEstimateUnion() {
        val bf = createFilter(this.testShape, TestingHashers.FROM1)
        val bf2 = createFilter(this.testShape, TestingHashers.FROM11)

        assertEquals(2, bf.estimateUnion(bf2))
        assertEquals(2, bf2.estimateUnion(bf))

        val bf3 = createEmptyFilter(this.testShape)

        assertEquals(1, bf.estimateUnion(bf3))
        assertEquals(1, bf3.estimateUnion(bf))
    }

    @Test
    fun testIndexExtractorMerge() {
        val shape = Shape.fromKM(5, 10)

        assertIndexExtractorMerge(shape, intArrayOf(0, 2, 4, 6, 8), intArrayOf(0, 2, 4, 6, 8))
        // test duplicate values
        assertIndexExtractorMerge(shape, intArrayOf(0, 2, 4, 2, 8), intArrayOf(0, 2, 4, 8))
        // test negative values
        assertFailedIndexExtractorConstructor(shape, intArrayOf(0, 2, 4, -2, 8))
        // test index too large
        assertFailedIndexExtractorConstructor(shape, intArrayOf(0, 2, 4, 12, 8))
        // test no indices
        assertIndexExtractorMerge(shape, IntArray(0), IntArray(0))
    }

    /**
     * Tests that isFull() returns the proper values.
     */
    @Test
    fun testIsFull() {
        // create empty filter

        var filter = createEmptyFilter(this.testShape)
        assertFalse(filter.isFull(), "Should not be full")

        filter = TestingHashers.populateEntireFilter(filter)
        assertTrue(filter.isFull(), "Should be full")

        filter = createFilter(this.testShape, IncrementingHasher(1, 3))
        assertFalse(filter.isFull(), "Should not be full")
    }

    /**
     * Tests that merging bloom filters works as expected with a generic BloomFilter.
     */
    @Test
    fun testMerge() {
        val bf1 = createFilter(this.testShape, TestingHashers.FROM1)
        val bf2 = createFilter(this.testShape, TestingHashers.FROM11)
        val bf3 = bf1.copy()
        bf3.merge(bf2)

        // test with BloomFilter
        val bf1Val: LongArray = bf1.asBitMapArray()
        val bf2Val: LongArray = bf2.asBitMapArray()
        for (i in bf1Val.indices) {
            bf1Val[i] = bf1Val[i] or bf2Val[i]
        }
        bf1.merge(bf2)

        val bf1New: LongArray = bf1.asBitMapArray()
        for (i in bf1Val.indices) {
            assertEquals(bf1Val[i], bf1New[i], "Bad value at " + i)
        }

        assertTrue(bf1.contains(bf2), "Should contain bf2")
        assertTrue(bf1.contains(bf3), "Should contain bf3")

        // test with hasher
        val bf4 = createFilter(this.testShape, TestingHashers.FROM1)
        bf4.merge(TestingHashers.FROM11)

        assertTrue(bf4.contains(bf2), "Should contain Bf2")
        assertTrue(bf4.contains(bf3), "Should contain Bf3")

        // test with hasher returning numbers out of range
        assertFailsWith<IllegalArgumentException>{ bf1.merge(BadHasher(bf1.shape.numberOfBits)) }
        assertFailsWith<IllegalArgumentException>{ bf1.merge(BadHasher(-1)) }

        // test error when bloom filter returns values out of range
        val s: Shape = Shape.fromKM(this.testShape.numberOfHashFunctions, this.testShape.numberOfBits * 3)
        val h: Hasher = IncrementingHasher(this.testShape.numberOfBits * 2L, 1)
        val bf5 = SimpleBloomFilter(s)
        bf5.merge(h)
        assertFailsWith<IllegalArgumentException>{ bf1.merge(bf5) }

        val bf6 = SparseBloomFilter(s)
        bf6.merge(h)
        assertFailsWith<IllegalArgumentException>{ bf1.merge(bf6) }
    }

    @Test
    fun testMergeWithBadHasher() {
        // value too large
        val f = createEmptyFilter(this.testShape)
        assertFailsWith<IllegalArgumentException>{ f.merge(BadHasher(this.testShape.numberOfBits)) }
        // negative value
        val f2 = createEmptyFilter(this.testShape)
        assertFailsWith<IllegalArgumentException>{ f2.merge(BadHasher(-1)) }
    }

//    @Test
//    fun testMergeWithBitMapExtractor() {
//        val bitMapCount: Int = BitMaps.numberOfBitMaps(this.testShape)
//        for (i in 0..4) {
//            val values = LongArray(bitMapCount)
//            for (idx in DefaultIndexExtractorTest.generateIntArray(
//                this.testShape.numberOfHashFunctions,
//                this.testShape.numberOfBits
//            )) {
//                BitMaps.set(values, idx)
//            }
//            val f = createFilter(this.testShape, BitMapExtractor.fromBitMapArray(values))
//            val lst: List<Long?> = ArrayList()
//            for (l in values) {
//                lst.add(l)
//            }
//            assertTrue(f.processBitMaps({ l -> lst.remove(java.lang.Long.valueOf(l)) }))
//            assertTrue(lst.isEmpty())
//        }
//        // values too large
//        val values = LongArray(bitMapCount)
//        fill(values, Long.Companion.MAX_VALUE)
//        val badExtractor: BitMapExtractor? = BitMapExtractor.fromBitMapArray(values)
//        val bf = createEmptyFilter(this.testShape)
//        assertFailsWith<IllegalArgumentException>{ bf.merge(badExtractor) })
//
//        // test where merged bits exceed expected bits but both bitmaps are the same length.
//        val badExtractor2: BitMapExtractor? = BitMapExtractor.fromBitMapArray(-0x8000000000000000L)
//        val bf2 = createEmptyFilter(Shape.fromKM(3, 32))
//        assertFailsWith<IllegalArgumentException>{ bf2.merge(badExtractor2) })
//    }
//
//    @Test
//    fun testMergeWithHasher() {
//        for (i in 0..4) {
//            val f = createEmptyFilter(this.testShape)
//            val expected: IntArray? = DefaultIndexExtractorTest.generateIntArray(
//                this.testShape.numberOfHashFunctions,
//                this.testShape.numberOfBits
//            )
//            val hasher: Hasher = ArrayHasher(expected)
//            f.merge(hasher)
//            // create sorted unique array of expected values
//            assertArrayEquals(DefaultIndexExtractorTest.unique(expected), f.asIndexArray())
//        }
//    }
//
//    @Test
//    fun testMergeWithIndexExtractor() {
//        for (i in 0..4) {
//            val values: IntArray? = DefaultIndexExtractorTest.generateIntArray(
//                this.testShape.numberOfHashFunctions,
//                this.testShape.numberOfBits
//            )
//            val f: BloomFilter = createFilter(this.testShape, IndexExtractor.fromIndexArray(values))
//            val uniqueValues: BitSet = DefaultIndexExtractorTest.uniqueSet(values)
//            assertTrue(f.processIndices({ idx ->
//                val result = uniqueValues.get(idx)
//                uniqueValues.clear(idx)
//                result
//            }))
//            assertTrue(uniqueValues.isEmpty())
//        }
//        // value to large
//        val f1: BloomFilter = createEmptyFilter(this.testShape)
//        assertThrows(
//            java.lang.IllegalArgumentException::class.java,
//            { f1.merge(IndexExtractor.fromIndexArray(this.testShape.numberOfBits)) })
//        // negative value
//        val f2: BloomFilter = createEmptyFilter(this.testShape)
//        assertThrows(
//            java.lang.IllegalArgumentException::class.java,
//            { f2.merge(IndexExtractor.fromIndexArray(-1)) })
//    }

    @Test
    fun testNegativeIntersection() {
        val p1 = IndexExtractor.fromIndexArray(
            0,
            1,
            2,
            3,
            4,
            5,
            6,
            7,
            8,
            9,
            10,
            11,
            12,
            13,
            20,
            26,
            28,
            30,
            32,
            34,
            35,
            36,
            37,
            39,
            40,
            41,
            42,
            43,
            45,
            46,
            48,
            49,
            50,
            51,
            52,
            53,
            54,
            55,
            56,
            57,
            58,
            59,
            60,
            61,
            62,
            63,
            64,
            65,
            66,
            67,
            68,
            69,
            70,
            71
        )
        val p2 = IndexExtractor.fromIndexArray(
            1,
            2,
            3,
            4,
            5,
            6,
            7,
            8,
            9,
            10,
            11,
            12,
            13,
            14,
            15,
            16,
            17,
            18,
            19,
            20,
            21,
            22,
            23,
            24,
            25,
            26,
            27
        )

        val filter1 = createEmptyFilter(Shape.fromKM(17, 72))
        filter1.merge(p1)
        val filter2 = createEmptyFilter(Shape.fromKM(17, 72))
        filter2.merge(p2)
        assertEquals(0, filter1.estimateIntersection(filter2))
    }
}
